package com.bangcommunity.bbframe.springredis.lock;

import com.bangcommunity.bbframe.common.exception.BaseRtmException;
import com.bangcommunity.bbframe.common.utils.lang.StringUtils;
import com.bangcommunity.bbframe.springredis.RedisClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import redis.clients.jedis.Jedis;

import java.util.function.Supplier;

/**
 * Created by tanghc on 17/1/23.
 */
public class RedisLock {
    static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private final static String STATUS_RETURN = "OK";

    private RedisLock() {
    }

    private static class RedisLockHolder {
        private static RedisLock instance = new RedisLock();
    }

    public static RedisLock getInstance() {
        return RedisLockHolder.instance;
    }

    /**
     * redis全局锁,一次获取不到等待1ms继续获取,最长等待maxWaitMs
     * 
     * @param lock
     *            锁标示
     * @param expireSec
     *            锁定时间,秒
     * @param maxWaitMs
     *            获取锁最长等待的时间,毫秒,超过这个时间则获取锁失败
     * @return
     */
    public boolean acquireLock(final String lock, final long expireSec, final long maxWaitMs) {
        logger.debug("acquireLock start:[lock={}][expireSec={}][maxWaitMs={}]", lock, expireSec, maxWaitMs);
        if (StringUtils.isBlank(lock)) {
            return false;
        }
        final String newlock = getLockKey(lock);
        long expireAt = System.currentTimeMillis() + maxWaitMs;
        boolean success = false;

        // 循环判断锁是否一直存在
        while (!success) {
            if (maxWaitMs > 0 && System.currentTimeMillis() > expireAt) {
                break;
            }
            success = RedisClient.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    try {
                        String acquired = "";
                        Jedis jedis = (Jedis) connection.getNativeConnection();
                        acquired = jedis.set(RedisClient.wrapPrefix(newlock), String.valueOf(expireSec), "NX", "EX",
                                expireSec);
                        if (STATUS_RETURN.equalsIgnoreCase(acquired)) {
                            return true;
                        } else {
                            Thread.sleep(10);
                            return false;
                        }
                    } catch (Exception e) {
                        logger.error("获取锁资源出现异常|lockKey:[{}]", lock, e);
                    }
                    return false;
                }
            });
        }
        return success;
    }

    private String getLockKey(String lock) {
        return "lock:" + lock;
    }

    /**
     * 解锁
     */
    public void releaseLock(final String lock) {
        logger.debug("releaseLock start:[lock={}]", lock);
        final String newlock = getLockKey(lock);
        RedisClient.del(newlock);
    }

    /**
     * 获取全局锁。一次获取不到则失败
     */
    public boolean acquireLockForOnce(final String lock, final int expireSec) {
        logger.debug("acquireLockForOnce start:[lock={}][expireSec={}]", lock, expireSec);
        if (StringUtils.isBlank(lock)) {
            return false;
        }
        final String newlock = getLockKey(lock);
        return RedisClient.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                try {
                    String acquired = "";
                    Jedis jedis = (Jedis) connection.getNativeConnection();
                    acquired = jedis.set(RedisClient.wrapPrefix(newlock), String.valueOf(expireSec), "NX", "EX",
                            expireSec);
                    if (STATUS_RETURN.equalsIgnoreCase(acquired)) {
                        return true;
                    } else {
                        Thread.sleep(1);
                        return false;
                    }
                } catch (Exception e) {
                    logger.error("获取锁资源出现异常|lockKey:[{}]", lock, e);
                }
                return false;
            }
        });
    }
    public <T> T execute(final String lockKey, final int expireSec,final long maxWaitMs,Supplier<T> supplier){
        try {
            boolean lock = acquireLock(lockKey,expireSec, maxWaitMs);
            if(lock){
                return supplier.get();
            }
            throw new BaseRtmException("系统繁忙,请稍后重试");
        } finally {
            try {
                releaseLock(lockKey);
            } catch (Exception e) {
                logger.info("release lock exception:{},{}",lockKey,e.getMessage());
            }
        }
    }
}
